import { segmentsIntersect } from './base';

/**
 * 节点树一维化
 * @param {any[]} nodes
 * @returns
 */
export const nodesTreeToList = (nodes) => {
  const list = [...nodes];
  for (let i = 0; i < list.length; i++) {
    const item = list[i];
    if (!item.nodes) continue;
    list.splice(i + 1, 0, ...item.nodes);
  }
  return list;
};

/**
 * 节点数组转换为节点映射对象
 * @param {any[]} nodes
 * @returns
 */
export const nodesToMap = (nodes) => {
  return nodes.reduce((map, node) => ((map[node.id] = node), map), {});
};

/**
 * 获取实际旋转缩放后的矩形
 * @param {number} x
 * @param {number} y
 * @param {number} width
 * @param {number} height
 * @param {number} rotation
 * @param {number} scale
 * @returns
 */
export const getRealRect = (x, y, width, height, rotation = 0, scale = 1) => {
  // 计算矩形中心点坐标
  const centerX = x + width / 2;
  const centerY = y + height / 2;

  // 旋转矩形
  const radian = rotation * (Math.PI / 180);
  const cosAngle = Math.cos(radian);
  const sinAngle = Math.sin(radian);

  // 缩放矩形
  const scaledWidth = width * scale;
  const scaledHeight = height * scale;

  // 矩形四个角相对于中心点的坐标
  const corner1X = -scaledWidth / 2;
  const corner1Y = -scaledHeight / 2;
  const corner2X = scaledWidth / 2;
  const corner2Y = -scaledHeight / 2;
  const corner3X = scaledWidth / 2;
  const corner3Y = scaledHeight / 2;
  const corner4X = -scaledWidth / 2;
  const corner4Y = scaledHeight / 2;

  // 旋转后四个角的坐标
  const rotatedCorner1X = centerX + corner1X * cosAngle - corner1Y * sinAngle;
  const rotatedCorner1Y = centerY + corner1X * sinAngle + corner1Y * cosAngle;
  const rotatedCorner2X = centerX + corner2X * cosAngle - corner2Y * sinAngle;
  const rotatedCorner2Y = centerY + corner2X * sinAngle + corner2Y * cosAngle;
  const rotatedCorner3X = centerX + corner3X * cosAngle - corner3Y * sinAngle;
  const rotatedCorner3Y = centerY + corner3X * sinAngle + corner3Y * cosAngle;
  const rotatedCorner4X = centerX + corner4X * cosAngle - corner4Y * sinAngle;
  const rotatedCorner4Y = centerY + corner4X * sinAngle + corner4Y * cosAngle;

  // 点集
  const points = [
    { x: rotatedCorner1X, y: rotatedCorner1Y },
    { x: rotatedCorner2X, y: rotatedCorner2Y },
    { x: rotatedCorner3X, y: rotatedCorner3Y },
    { x: rotatedCorner4X, y: rotatedCorner4Y },
  ];
  // 边集
  const edges = points.map((point, index, array) => ({ o: point, d: array[(index + 1) % array.length] }));
  return {
    points,
    edges,
    center: { x: centerX, y: centerY },
  };
};

/**
 * 获取最小外接矩形
 * @param {{x:number, y:number}[]} points
 * @returns
 */
export const getMinEnclosingRect = (points) => {
  // 计算最小外接矩形的左上角坐标、右下角坐标和宽高
  const minX = Math.min(...points.map((point) => point.x));
  const maxX = Math.max(...points.map((point) => point.x));
  const minY = Math.min(...points.map((point) => point.y));
  const maxY = Math.max(...points.map((point) => point.y));
  const minEnclosingRectWidth = maxX - minX;
  const minEnclosingRectHeight = maxY - minY;

  return {
    left: minX,
    top: minY,
    right: maxX,
    bottom: maxY,
    centerX: minX + minEnclosingRectWidth / 2,
    centerY: minY + minEnclosingRectHeight / 2,
    width: minEnclosingRectWidth,
    height: minEnclosingRectHeight,
  };
};

/**
 * 获取多个节点的最小外接矩形
 * @param {any[]} nodes
 * @param {boolean} useGridCoord
 * @returns
 */
export const getNodesMinEnclosingRect = (nodes, useGridCoord = false) => {
  // 没有节点
  if (!nodes.length) return null;
  // 获取最小外接矩形坐标
  let minX = null;
  let minY = null;
  let maxX = null;
  let maxY = null;
  nodes.forEach((node) => {
    const { left, top, right, bottom } = useGridCoord ? node.gridMinEnclosingRect : node.minEnclosingRect;
    if (minX === null || minX > left) minX = left;
    if (minY === null || minY > top) minY = top;
    if (maxX === null || maxX < right) maxX = right;
    if (maxY === null || maxY < bottom) maxY = bottom;
  });
  // 得到最小外接矩形宽高
  // @ts-ignore
  const rectWidth = maxX - minX;
  // @ts-ignore
  const rectHeight = maxY - minY;
  // 最小外接矩形没有宽高
  if (rectWidth <= 0 || rectHeight <= 0) return null;
  return {
    left: minX,
    top: minY,
    right: maxX,
    bottom: maxY,
    // @ts-ignore
    centerX: minX + rectWidth / 2,
    // @ts-ignore
    centerY: minY + rectHeight / 2,
    width: rectWidth,
    height: rectHeight,
  };
};

/**
 * 交叉数法
 * 以某一点做水平向右的射线，获取该射线与多边形的边相交的次数
 * 奇数时，该点在多边形内部，否则在多边形外部
 * @param {{x:number,y:number}} point 目标点
 * @param {{o:{x:number,y:number},d:{x:number,y:number}}[]} edges 多边形边集
 * @returns {number}
 */
export const getCrossingNumber = (point, edges) => {
  let count = 0;
  edges.forEach((edge) => {
    // o, d 是边的起点和终点
    const { o, d } = edge;
    // 起点和终点位于水平射线的两侧才会有交点
    if (o.y > point.y !== d.y > point.y) {
      // 点斜式的直线方程公式： y - y0 = k * (x - x0)
      // x = (y - y0) / k + x0
      const k = (d.y - o.y) / (d.x - o.x);
      const x = (point.y - o.y) / k + o.x;
      if (x > point.x) count += 1;
    }
  });
  return count;
};

/**
 * 判断节点是否在框选矩形中
 * @param {*} node
 * @param {{x:number,y:number,width:number,height:number}} regionRect
 * @returns
 */
export const isNodeInRegionRect = (node, regionRect) => {
  const { x, y, width, height } = regionRect;
  // 获取节点相对网格坐标的矩形
  const { points, edges } = node.gridRect;
  // 判断节点是否有顶点在矩形内部
  const hasPointInRect = points.some((point) => {
    return point.x > x && point.x < x + width && point.y > y && point.y < y + height;
  });
  if (hasPointInRect) return true;
  // 获取矩形边集
  const { edges: rectEdges } = getRealRect(x, y, width, height);
  // 判断矩形边与节点边是否有相交点
  return rectEdges.some((rectEdge) => edges.some((edge) => segmentsIntersect(rectEdge, edge)));
};

/**
 * 判断节点是否在矩形中
 * @param {*} node1 节点1
 * @param {*} node2 节点2
 * @returns
 */
export const isNodeColliding = (node1, node2) => {
  // 获取节点1相对网格坐标的矩形
  const { edges: node1Edges, points: node1Points } = node1.gridRect;
  // 获取节点2相对网格坐标的矩形
  const { edges: node2Edges, points: node2Points } = node2.gridRect;
  // 判断节点1中的点是否在节点2中
  const isNode1PointInNode2 = node1Points.some((point) => !!(getCrossingNumber(point, node2Edges) % 2));
  if (isNode1PointInNode2) return true;
  // 判断节点2中的点是否在节点1中
  const isNode2PointInNode1 = node2Points.some((point) => !!(getCrossingNumber(point, node1Edges) % 2));
  if (isNode2PointInNode1) return true;
  // 判断两矩形边是否有相交点
  return node2Edges.some((edge2) => node1Edges.some((edge1) => segmentsIntersect(edge2, edge1)));
};
